{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "import random,datetime\n",
    "from bisect import bisect_left\n",
    "from enum import Enum\n",
    "from math import exp\n",
    "import math\n",
    "from itertools import chain\n",
    "import matplotlib.pyplot as plt"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "def _mutate_custom(parent, custom_mutate, get_fitness):\n",
    "    childGenes = parent.Genes[:]\n",
    "    custom_mutate(childGenes)\n",
    "    fitness = get_fitness(childGenes)\n",
    "    return Chromosome(childGenes, fitness, Strategies.Mutate)\n",
    "\n",
    "def _crossover(parentGenes, index, parents, get_fitness, crossover, mutate,\n",
    "               generate_parent):\n",
    "    donorIndex = random.randrange(0, len(parents))\n",
    "    if donorIndex == index:\n",
    "        donorIndex = (donorIndex + 1) % len(parents)\n",
    "    childGenes = crossover(parentGenes, parents[donorIndex].Genes)\n",
    "    if childGenes is None:\n",
    "        parents[donorIndex] = generate_parent()\n",
    "        return mutate(parents[index])\n",
    "    fitness = get_fitness(childGenes)\n",
    "    return Chromosome(childGenes, fitness, Strategies.Crossover)\n",
    "\n",
    "\n",
    "def get_best(get_fitness, targetLen, optimalFitness, geneSet, display,\n",
    "             custom_mutate=None, custom_create=None, maxAge=None,\n",
    "             poolSize=1, crossover=None):\n",
    "    if custom_mutate is None:\n",
    "        def fnMutate(parent):\n",
    "            return _mutate(parent, geneSet, get_fitness)\n",
    "    else:\n",
    "        def fnMutate(parent):\n",
    "            return _mutate_custom(parent, custom_mutate, get_fitness)\n",
    "\n",
    "    if custom_create is None:\n",
    "        def fnGenerateParent():\n",
    "            return _generate_parent(targetLen, geneSet, get_fitness)\n",
    "    else:\n",
    "        def fnGenerateParent():\n",
    "            genes = custom_create()\n",
    "            return Chromosome(genes, get_fitness(genes), Strategies.Create)\n",
    "\n",
    "    strategyLookup = {\n",
    "        Strategies.Create: lambda p, i, o: fnGenerateParent(),\n",
    "        Strategies.Mutate: lambda p, i, o: fnMutate(p),\n",
    "        Strategies.Crossover: lambda p, i, o:\n",
    "        _crossover(p.Genes, i, o, get_fitness, crossover, fnMutate,\n",
    "                   fnGenerateParent)\n",
    "    }\n",
    "\n",
    "    usedStrategies = [strategyLookup[Strategies.Mutate]]\n",
    "    if crossover is not None:\n",
    "        usedStrategies.append(strategyLookup[Strategies.Crossover])\n",
    "\n",
    "        def fnNewChild(parent, index, parents):\n",
    "            return random.choice(usedStrategies)(parent, index, parents)\n",
    "    else:\n",
    "        def fnNewChild(parent, index, parents):\n",
    "            return fnMutate(parent)\n",
    "\n",
    "    for improvement in _get_improvement(fnNewChild, fnGenerateParent,\n",
    "                                        maxAge, poolSize):\n",
    "        display(improvement)\n",
    "        f = strategyLookup[improvement.Strategy]\n",
    "        usedStrategies.append(f)\n",
    "        if not optimalFitness > improvement.Fitness:\n",
    "            return improvement\n",
    "\n",
    "\n",
    "def _get_improvement(new_child, generate_parent, maxAge, poolSize):\n",
    "    bestParent = generate_parent()\n",
    "    yield bestParent\n",
    "    parents = [bestParent]\n",
    "    historicalFitnesses = [bestParent.Fitness]\n",
    "    for _ in range(poolSize - 1):\n",
    "        parent = generate_parent()\n",
    "        if parent.Fitness > bestParent.Fitness:\n",
    "            yield parent\n",
    "            bestParent = parent\n",
    "            historicalFitnesses.append(parent.Fitness)\n",
    "        parents.append(parent)\n",
    "    lastParentIndex = poolSize - 1\n",
    "    pindex = 1\n",
    "    while True:\n",
    "        pindex = pindex - 1 if pindex > 0 else lastParentIndex\n",
    "        parent = parents[pindex]\n",
    "        child = new_child(parent, pindex, parents)\n",
    "        if parent.Fitness > child.Fitness:\n",
    "            if maxAge is None:\n",
    "                continue\n",
    "            parent.Age += 1\n",
    "            if maxAge > parent.Age:\n",
    "                continue\n",
    "            index = bisect_left(historicalFitnesses, child.Fitness, 0,\n",
    "                                len(historicalFitnesses))\n",
    "            proportionSimilar = index / len(historicalFitnesses)\n",
    "            if random.random() < exp(-proportionSimilar):\n",
    "                parents[pindex] = child\n",
    "                continue\n",
    "            bestParent.Age = 0\n",
    "            parents[pindex] = bestParent\n",
    "            continue\n",
    "        if not child.Fitness > parent.Fitness:\n",
    "            child.Age = parent.Age + 1\n",
    "            parents[pindex] = child\n",
    "            continue\n",
    "        child.Age = 0\n",
    "        parents[pindex] = child\n",
    "        if child.Fitness > bestParent.Fitness:\n",
    "            bestParent = child\n",
    "            yield bestParent\n",
    "            historicalFitnesses.append(bestParent.Fitness)\n",
    "\n",
    "class Chromosome:\n",
    "    def __init__(self, genes, fitness, strategy):\n",
    "        self.Genes = genes\n",
    "        self.Fitness = fitness\n",
    "        self.Strategy = strategy\n",
    "        self.Age = 0\n",
    "\n",
    "class Strategies(Enum):\n",
    "    Create = 0,\n",
    "    Mutate = 1,\n",
    "    Crossover = 2"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "def get_fitness(genes, idToLocationLookup):\n",
    "    fitness = get_distance(idToLocationLookup[genes[0]],\n",
    "                           idToLocationLookup[genes[-1]])\n",
    "\n",
    "    for i in range(len(genes) - 1):\n",
    "        start = idToLocationLookup[genes[i]]\n",
    "        end = idToLocationLookup[genes[i + 1]]\n",
    "        fitness += get_distance(start, end)\n",
    "\n",
    "    return Fitness(round(fitness, 2))\n",
    "\n",
    "\n",
    "def display(candidate, startTime):\n",
    "    timeDiff = datetime.datetime.now() - startTime\n",
    "    print(\"{}\\t{}\\t{}\\t{}\".format(\n",
    "        ' '.join(map(str, candidate.Genes)),\n",
    "        candidate.Fitness,\n",
    "        candidate.Strategy.name,\n",
    "        timeDiff))\n",
    "\n",
    "def display2(loc,gens):\n",
    "    x ,y ,n= [],[],[]\n",
    "    for i in loc:\n",
    "        x.append(loc[i][0])\n",
    "        y.append(loc[i][1])\n",
    "        n.append(i)\n",
    "    plt.scatter(x,y)\n",
    "    for i in range(len(x)):\n",
    "        plt.text(x[i]-0.3,y[i]-0.1,n[i])\n",
    "    for i in range(len(gens)-1):\n",
    "        k = gens[i]\n",
    "        k2 = gens[i+1]\n",
    "        d = round(get_distance(loc[k],loc[k2]),2)\n",
    "        plt.annotate(d,xy=(loc[k2][0], loc[k2][1]),xytext=(loc[k][0], loc[k][1]),\n",
    "                    arrowprops=dict(arrowstyle=\"->\", color=\"r\"))\n",
    "    plt.title(\"->\".join(gens))\n",
    "\n",
    "def get_distance(locationA, locationB):\n",
    "    sideA = locationA[0] - locationB[0]\n",
    "    sideB = locationA[1] - locationB[1]\n",
    "    sideC = math.sqrt(sideA * sideA + sideB * sideB)\n",
    "    return sideC\n",
    "\n",
    "\n",
    "def mutate(genes, fnGetFitness):\n",
    "    count = random.randint(2, len(genes))\n",
    "    initialFitness = fnGetFitness(genes)\n",
    "    while count > 0:\n",
    "        count -= 1\n",
    "        indexA, indexB = random.sample(range(len(genes)), 2)\n",
    "        genes[indexA], genes[indexB] = genes[indexB], genes[indexA]\n",
    "        fitness = fnGetFitness(genes)\n",
    "        if fitness > initialFitness:\n",
    "            return\n",
    "\n",
    "\n",
    "def crossover(parentGenes, donorGenes, fnGetFitness):\n",
    "\n",
    "    pairs = {Pair(donorGenes[0], donorGenes[-1]): 0}\n",
    "\n",
    "    for i in range(len(donorGenes) - 1):\n",
    "        pairs[Pair(donorGenes[i], donorGenes[i + 1])] = 0\n",
    "\n",
    "    tempGenes = parentGenes[:]\n",
    "    if Pair(parentGenes[0], parentGenes[-1]) in pairs:\n",
    "        found = False\n",
    "        for i in range(len(parentGenes) - 1):\n",
    "            if Pair(parentGenes[i], parentGenes[i + 1]) in pairs:\n",
    "                continue\n",
    "            tempGenes = parentGenes[i + 1:] + parentGenes[:i + 1]\n",
    "            found = True\n",
    "            break\n",
    "        if not found:\n",
    "            return None\n",
    "\n",
    "    runs = [[tempGenes[0]]]\n",
    "    for i in range(len(tempGenes) - 1):\n",
    "        if Pair(tempGenes[i], tempGenes[i + 1]) in pairs:\n",
    "            runs[-1].append(tempGenes[i + 1])\n",
    "            continue\n",
    "        runs.append([tempGenes[i + 1]])\n",
    "\n",
    "    initialFitness = fnGetFitness(parentGenes)\n",
    "    count = random.randint(2, 20)\n",
    "    runIndexes = range(len(runs))\n",
    "    while count > 0:\n",
    "        count -= 1\n",
    "        for i in runIndexes:\n",
    "            if len(runs[i]) == 1:\n",
    "                continue\n",
    "            if random.randint(0, len(runs)) == 0:\n",
    "                runs[i] = [n for n in reversed(runs[i])]\n",
    "\n",
    "        indexA, indexB = random.sample(runIndexes, 2)\n",
    "        runs[indexA], runs[indexB] = runs[indexB], runs[indexA]\n",
    "        childGenes = list(chain.from_iterable(runs))\n",
    "        if fnGetFitness(childGenes) > initialFitness:\n",
    "            return childGenes\n",
    "    return childGenes"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Fitness:\n",
    "    def __init__(self, totalDistance):\n",
    "        self.TotalDistance = totalDistance\n",
    "\n",
    "    def __gt__(self, other):\n",
    "        return self.TotalDistance < other.TotalDistance\n",
    "\n",
    "    def __str__(self):\n",
    "        return \"{:0.2f}\".format(self.TotalDistance)\n",
    "\n",
    "\n",
    "class Pair:\n",
    "    def __init__(self, node, adjacent):\n",
    "        if node < adjacent:\n",
    "            node, adjacent = adjacent, node\n",
    "        self.Node = node\n",
    "        self.Adjacent = adjacent\n",
    "\n",
    "    def __eq__(self, other):\n",
    "        return self.Node == other.Node and self.Adjacent == other.Adjacent\n",
    "\n",
    "    def __hash__(self):\n",
    "        return hash(self.Node) * 397 ^ hash(self.Adjacent)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "idToLocationLookup = {\n",
    "            'A': [4, 7],\n",
    "            'B': [2, 6],\n",
    "            'C': [0, 5],\n",
    "            'D': [1, 3],\n",
    "            'E': [3, 0],\n",
    "            'F': [5, 1],\n",
    "            'G': [7, 2],\n",
    "            'H': [6, 4]}\n",
    "optimalSequence = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H']"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "def solve(idToLocationLookup, optimalSequence):\n",
    "        geneset = [i for i in idToLocationLookup.keys()]\n",
    "\n",
    "        def fnCreate():\n",
    "            return random.sample(geneset, len(geneset))\n",
    "\n",
    "        def fnDisplay(candidate):\n",
    "            display(candidate, startTime)\n",
    "\n",
    "        def fnGetFitness(genes):\n",
    "            return get_fitness(genes, idToLocationLookup)\n",
    "\n",
    "        def fnMutate(genes):\n",
    "            mutate(genes, fnGetFitness)\n",
    "\n",
    "        def fnCrossover(parent, donor):\n",
    "            return crossover(parent, donor, fnGetFitness)\n",
    "\n",
    "        optimalFitness = fnGetFitness(optimalSequence)\n",
    "        startTime = datetime.datetime.now()\n",
    "        best = get_best(fnGetFitness, None, optimalFitness, None,\n",
    "                                fnDisplay, fnMutate, fnCreate, maxAge=500,\n",
    "                                poolSize=25, crossover=fnCrossover)\n",
    "        return best"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "G C H E F D B A\t36.64\tCreate\t0:00:00\n",
      "A B D C H G F E\t27.50\tCreate\t0:00:00.001000\n",
      "G F H A B C D E\t23.79\tCreate\t0:00:00.001000\n",
      "C E F G H A B D\t23.78\tCreate\t0:00:00.001995\n",
      "H G E F D C B A\t23.73\tCreate\t0:00:00.003988\n",
      "C D E F G H A B\t20.63\tCrossover\t0:00:00.014960\n"
     ]
    }
   ],
   "source": [
    "x = solve(idToLocationLookup, optimalSequence)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "['C', 'D', 'E', 'F', 'G', 'H', 'A', 'B']"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "x.Genes"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "display2(idToLocationLookup,x.Genes)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.10"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
